﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.RegularExpressions;

namespace RecipesManager.RecipesImporter
{
    //Classe per importare una ricetta da file
    public class RecipeImporter
    {
      #region PrivateData
      private static HashSet<string> keyWords;
      private static HashSet<string> alreadyParsedKeyWords;
      private static HashSet<string> alreadyImportedIngredients;
      private static int index;
      private static ImportedRecipe recipe;
      private static string parsingMode;
      private static string[] lines;

      static RecipeImporter()
      {
        lines = null;
        index = 0;
        parsingMode = "waitForKeyWord";

        keyWords = new HashSet<string>();
        keyWords.Add("nome");
        keyWords.Add("originale");
        keyWords.Add("tempo");
        keyWords.Add("porzioni");
        keyWords.Add("misura");
        keyWords.Add("ingredienti");
        keyWords.Add("passi");
        keyWords.Add("prove");

        alreadyParsedKeyWords = new HashSet<string>();
        alreadyImportedIngredients = new HashSet<string>();
      }
      #endregion


      public static ImportedRecipe importRecipe(string filePath)
      {
        initializeParser(filePath);

        for (index = 0; index < lines.Length; index++)
        {
          if (isKeyWordLine())
          { 
            parsingMode = getCurrentKeyWord();
            alreadyParsedKeyWords.Add(parsingMode);
          }
          else
            parsingMode = "waitForKeyWord";

          switch (parsingMode)
          {
            case "nome":
                        manageName();
                        break;
            case "originale":
                        manageOriginalSource();
                        break;
            case "tempo":
                        manageExecutionTime();
                        break;
            case "porzioni":
                        manageServings();
                        break;
            case "misura":
                        manageMeasuringUnit();
                        break;
            case "ingredienti":
                        manageIngredients();
                        break;
            case "passi":
                        manageSteps();
                        break;
            case "prove":
                        manageTrials();
                        break;
            default:    
                        break;
          }

        }

        if (recipe.Name.Equals(""))
          throw new MissingRecipeNameException();
        else if (!InputValidator.validateRecipe(recipe))
          throw new ParsingException();
        
        return recipe;
      }


      #region SupportMethods
      private static void  initializeParser(string filePath)
      {
        lines = System.IO.File.ReadAllLines(filePath);
        recipe = new ImportedRecipe();
        alreadyParsedKeyWords = new HashSet<string>();
        alreadyImportedIngredients = new HashSet<string>();
      }

      private static string getCurrentKeyWord()
      {
        return lines[index].Trim().ToLower();
      }

      private static bool isAlreadyParsedKeyWordLine()
      {
        foreach (var k in alreadyParsedKeyWords)
        {
          if (lines[index].Trim().ToLower().Equals(k))
            return true;
        }
        return false;
      }

      private static bool isAlreadyImportedIngredient(string ingredient)
      {
          foreach (var i in alreadyImportedIngredients)
          {
            if (ingredient.ToLower().Equals(i))
              return true;
          }
          return false;
      }

      private static bool isKeyWordLine()
      {
        if (isAlreadyParsedKeyWordLine())
          throw new DuplicateKeywordException();

        foreach (var k in keyWords)
        {
          if (lines[index].Trim().ToLower().Equals(k))
            return true;
        }
        return false;
      }
      private static void nextLine()
      {
        if (index + 1 <= lines.Length)
            index++;
      }

      private static void skipEmptyLines()
      {
        while (!(isEndOfFile()) && lines[index].Trim().Equals(""))
          nextLine();
      }

      private static void nextMeaningfulLine()
      {
        nextLine();
        skipEmptyLines();
      }

      private static bool isEndOfFile()
      {
        return index >= lines.Length - 1;
      }

      private static bool isNumber(string s)
      {
        foreach (char c in s)
        {
            if (!Char.IsDigit(c))
                return false;
        }
        return (s.Length > 0);
      }
      //Per non far saltare una keyword al main loop
      private static void synchronizeWithKeyWord()
      {
        index--;
      }
      #endregion


      #region KeyWordsManagementMethods
      private static void manageName()
      {
        nextMeaningfulLine();
        recipe.Name = lines[index].Trim();
      }

      private static void manageOriginalSource()
      {
        nextMeaningfulLine();
        recipe.OriginalSource = lines[index].Trim();
      }

      private static void manageExecutionTime()
      {
        nextMeaningfulLine();
        string preparationTime = Regex.Replace(lines[index], @"\s+", " ");//Cambia ogni carattere vuoto con un solo whitespace

        if (preparationTime.Trim().Split(' ').Length == 2)//Pattern 1
          manageExecutionTimePattern1(preparationTime);
        else if (preparationTime.Trim().Split(' ').Length == 4)//Pattern 2
          manageExecutionTimePattern2(preparationTime);
      }

      private static void manageServings()
      {
        nextMeaningfulLine();
        if(isNumber(lines[index].Trim()))
          recipe.Servings = Convert.ToInt32(lines[index].Trim());
      }

      private static void manageMeasuringUnit()
      {
        nextMeaningfulLine();
        recipe.MeasuringUnit = lines[index].Trim().ToLower();
      }

      private static void manageIngredients()
      {
        ImportedIngredient ingredient;
        nextMeaningfulLine();

        while (!isKeyWordLine())
        {
          ingredient = manageIngredient();

          if (ingredient != null)
          {
            if (!isAlreadyImportedIngredient(ingredient.Name))
            {
              recipe.Ingredients.Add(ingredient);
              alreadyImportedIngredients.Add(ingredient.Name);
            }
            else
              throw new DuplicateIngredientException();
          }

          if (isEndOfFile())
            break;

          nextMeaningfulLine();
        }
        synchronizeWithKeyWord();
      }

      private static void manageSteps()
      {
        string allStepsInOneString = "";
        nextMeaningfulLine();

        while (!isKeyWordLine())
        {
          allStepsInOneString = allStepsInOneString + lines[index].Trim().ToLower();
          
          if (isEndOfFile())
            break;

          nextMeaningfulLine();
        }

        synchronizeWithKeyWord();

        foreach (string s in allStepsInOneString.Split('.'))
        {
          if(!s.Equals(""))
            recipe.Steps.Add(s.Trim());
        }
        
      }

      private static void manageTrials()
      {
        nextMeaningfulLine();

        while (!isKeyWordLine())
        {
          recipe.Trials.Add(lines[index].Trim().ToLower());

          nextMeaningfulLine();

          if(isEndOfFile())
            break;
        }
        synchronizeWithKeyWord();

      }
      #endregion


      #region SupportManagementMethods
      private static void manageExecutionTimePattern1(string preparationTime)
      {
        recipe.PreparationHours = Convert.ToInt32(preparationTime.Trim().Split(' ')[0]);
        recipe.PreparationMinutes = Convert.ToInt32(preparationTime.Trim().Split(' ')[1]);
      }

      private static void manageExecutionTimePattern2(string preparationTime)
      {
        if (preparationTime.Trim().Split(' ')[1].Equals("ore") &&
            preparationTime.Trim().Split(' ')[3].Equals("minuti"))
        {
          recipe.PreparationHours = Convert.ToInt32(preparationTime.Trim().Split(' ')[0]);
          recipe.PreparationMinutes = Convert.ToInt32(preparationTime.Trim().Split(' ')[2]);
        }
      }

      private static ImportedIngredient manageIngredient()
      {
        string ingredient = Regex.Replace(lines[index], @"\s+", " ");
        string[] stringsIngredient = ingredient.Split(' ');
        int length = stringsIngredient.Length;
        string name = "";
        string quantity = "";
        string note = "";

        if (ingredient.Trim().Split('|').Length == 3)//ing | kg 1 | note
        {
          string[] ingredientSlots = ingredient.Split('|');
          name = ingredientSlots[0].Trim();
          quantity = ingredientSlots[1].Trim();
          note = ingredientSlots[2].Trim();
        }
        else if ((length - 2) >= 0 && isNumber(stringsIngredient[length - 2]))//ing 1 kg
        {
          quantity = stringsIngredient[length - 2] + " " + stringsIngredient[length - 1];
          for (int i = 0; i < length - 2; i++)
            name = name + stringsIngredient[i] + " ";
        }
        else if ((length - 1) >= 0 && isNumber(stringsIngredient[length - 1]))//ing kg 1
        {
          quantity = stringsIngredient[length - 1] + " " + stringsIngredient[length - 2];
          for (int i = 0; i < length - 2; i++)
            name = name + stringsIngredient[i] + " ";
        }
        else
          return null;

        //Se ha rispettato almeno un pattern
        return new ImportedIngredient(name.Trim(), quantity, note);
      }
      #endregion


    }
}
